#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;

namespace BruteForceSerializer.Normalization
{
	public static class TypeExtensions
	{
		//private static readonly Dictionary<Type, Dictionary<string, FieldInfo>> typeFieldCache = new Dictionary<Type, Dictionary<string, FieldInfo>>();
		//private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> typePropertyCache = new Dictionary<Type, Dictionary<string, PropertyInfo>>();

		public static bool IsSimpleType(this Type type)
		{
			var typeInfo = type.GetTypeInfo();
			return typeInfo.IsPrimitive ||
				   typeInfo.IsEnum ||
			       type == typeof(string) ||
			       type == typeof(decimal) ||
			       type == typeof(DateTime);
		}

		internal static object ConvertValue(this Type type, object fieldValue)
		{
			if (fieldValue is long)
			{
				if (type.GetTypeInfo().IsEnum)
					type = Enum.GetUnderlyingType(type);
				if (type == typeof(int))
					return (int)(long)fieldValue;
				if (type == typeof(Int16))
					return (Int16)(long)fieldValue;
				if (type == typeof(byte))
					return (byte)(long)fieldValue;
				//if (type == typeof(decimal))
				//	return (decimal)(long)fieldValue;
				if (type == typeof(IntPtr))
					return new IntPtr((long)fieldValue);

			}
			//if (fieldValue is INormalizedObject)
			//	throw new Exception("Deserialization failed!");
			return fieldValue;
		}

		//private static void AddTypeToCache(Type type)
		//{
		//    if (typeFieldCache.ContainsKey(type))
		//        return;
		//    lock (typeFieldCache)
		//    {
		//        typeFieldCache[type] = type.GetFields(BindingFlags.Instance | BindingFlags.Public).Where(f => !f.IsNotSerialized).ToDictionary(f => f.Name, f => f);
		//    }
		//    lock (typePropertyCache)
		//    {
		//        typePropertyCache[type] = type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0).ToDictionary(p => p.Name, p => p);
		//    }
		//}

		internal static IEnumerable<FieldInfo> GetSerializableFields(this Type type)
		{
			//AddTypeToCache(type);
			//return typeFieldCache[type].Values;
			return type.GetRuntimeFields().Where(f => !f.IsStatic && f.IsPublic); //.GetFields(BindingFlags.Instance | BindingFlags.Public).Where(f => !f.IsNotSerialized);
		}

		internal static IEnumerable<PropertyInfo> GetSerializableProperties(this Type type)
		{
			//AddTypeToCache(type);
			//return typePropertyCache[type].Values;
			return type.GetRuntimeProperties() //GetProperties(BindingFlags.Instance | BindingFlags.Public)
				.Where(p => p.CanRead && p.CanWrite && //must have getter and setter
					p.GetMethod != null && p.GetMethod.IsPublic && p.SetMethod != null && p.SetMethod.IsPublic && //getter and setter must both be public
					!p.GetMethod.IsStatic && //must not be static
					p.GetIndexParameters().Length == 0); //must not be an index property
		}

		internal static void SetSerializableFieldOrPropertyValue(this Type type, object instance, string fieldName, object fieldValue)
		{
			//AddTypeToCache(type);
			//FieldInfo fieldInfo = null;
			//if (typeFieldCache[type].TryGetValue(fieldName, out fieldInfo))
			var fieldInfo = type.GetRuntimeField(fieldName);
			if (fieldInfo != null)
			{
				fieldInfo.SetValue(instance, fieldInfo.FieldType.ConvertValue(fieldValue));
			}
			else
			{
				//PropertyInfo propInfo = null;
				//if (typePropertyCache[type].TryGetValue(fieldName, out propInfo))
				var propInfo = type.GetRuntimeProperty(fieldName);
				if (propInfo != null)
				{
					if (!propInfo.CanWrite)
						Debug.WriteLine("BruteForceSerializer Warning: Cannot write to property " + fieldName + " of type " + type + " because it is readonly.");
					else
						propInfo.SetValue(instance, propInfo.PropertyType.ConvertValue(fieldValue), null);
				}

				//... simply ignore when a field/property is not found, for more compatibility between different versions of a type
				//else
				//	throw new Exception("Error setting field: Could not find field or property " + fieldName + " within type " + type);
			}
		}
	}
}